/******************************************************************************/
package jamie.org.sil.www.usxUtils;

import java.io.IOException;


/******************************************************************************/
/**
 * An incident handler.
 * <p>
 *
 * The following discussion covers exception handling and logging in general,
 * and therefore has a bearing upon the present class, and also all of the
 * ConverterException classes and the LogManager.
 * <p>
 * 
 * Error handling is a complicated subject.  In general, the questions are
 * a) what action do we take as a result of a problem; and b) do we wish to
 * log the fact that the problem has been detected?
 * <p>
 * 
 * In the general case, knowledge about this is distributed throughout the
 * system.  The low level code which detects the problem knows precisely
 * what the issue is and how severe it potentially is; but only the higher
 * level code knows whether there are strategies in place to recover (in
 * which case, no matter what the lower level code anticipates may be the
 * consequences of the problem, it is, in fact, nothing to worry about).
 * <p>
 * 
 * The problem, though, is that if we are to leave the decision to the
 * higher level code, that code becomes polluted by the requirement to
 * have a detailed understanding of all the possible issues which may
 * crop up below it, and needs to be modified whenever any of the lower
 * level infrastructure changes.  But on the other hand if we <i>don't</i>
 * work this way, we risk having the low level code make decisions which
 * are incorrect because it has no knowledge of what is happening above
 * it.
 * <p>
 * 
 * In fact, at the time of writing, I have yet to identify in this
 * particular system any situation in which it is unsafe to leave the
 * lower level code to make up its own mind about things -- and if such
 * a situation crops up in future, we'll just have to cross that bridge
 * when we come to it.
 * <p>
 * 
 * The philosophy, then, is as follows.  First, I recognise two phases to
 * processing.
 * <p>
 *
 * In one, we are processing individual input files.  An exception
 * here (or at least, one which reflects a genuine problem) will
 * certainly compromise the processing of the present file.  If
 * possible, it may make sense to continue processing the present file
 * in case other errors can be reported on this run, but that may not
 * always be appropriate -- we may need to abandon work on the file
 * immediately.  And in particularly severe cases, we may actually
 * need to abandon the run completely.
 * <p>
 *
 * In the other phase of processing, we are not processing individual
 * files -- we may be initialising things, or we may be sorting things
 * out at the end of the run.  Here, exceptions are likely to require
 * us to abandon things altogether.  We may need to do so immediately,
 * or, as above, we may want to soldier on for a while, to see if we
 * can detect and report other issues.
 * <p>
 *
 * To handle this, when things raise {@link ConverterException}, they
 * indicate the action to be taken -- abandon current file now /
 * abandon current file later / abandon run now / abandon run later /
 * no action.  They also indicate whether the issue should be logged,
 * and if so, whether it counts as an error, a warning, or an
 * information.
 * <p>
 *
 * ConverterException reports to the present class.  This interacts
 * with the {@link LogManager} to arrange for any necessary logging to
 * occur.  It also takes action as appropriate.  If action has been
 * requested immediately, it raises either {@link
 * ConverterExceptionAbandonRun} or {@link
 * ConverterExceptionAbandonFile} as appropriate.  Otherwise it makes
 * a note of the required action, for use later.
 * <p>
 *
 * There is one complication, in that some low level code may be used
 * both during initialisation etc and also during file processing,
 * and it therefore cannot know whether it should request that the
 * <i>run</i> be abandoned, or only the present <i>file</i>.  In
 * such cases it assumes the latter, relying on code further up to
 * take appropriate action where in fact we need to abandon the run.
 * <p>
 * 
 * To get a handle on the implications of this, let's take a look at
 * how the higher level code should be written to interact with the
 * present class.
 * <p>
 * 
 * Let's assume a simple case in which you have some initialisation
 * processing (during which you are not processing individual
 * input files), followed by something which iterates over the
 * individual input files.  I recommend something like this :-
 * <p>
 * 
 * In the main program, we catch ConverterAbandonSomething
 * exceptions, the assumption being that lower level processing
 * will have responded to them as necessary, but may then have
 * rethrown them to ensure that we abandon processing.
 * <p>
 *
 * We also catch general exceptions, so as to make sure we can trap and
 * report on anything for which we were not otherwise set up to cater.
 * In this case, we definitely have an error, and for safety's sake,
 * we have to assume that the error is such that all output is
 * invalid.
 * <p>
 *
 * In the {@code finally} block, we check what the worst action
 * recorded by the present class is.  If any action at all has
 * been required, it is indicative that we have had some kind of
 * problem, and we assume that this will have invalidated any
 * output which may have been generated.
 * <p>
 *
 * <code>
 * main ()
 * {
 *   try
 *   {
 *     doInitialisation();
 *     processFiles();
 *   }
 *
 *   catch (ConverterExceptionAbandonSomething e)
 *   {
 *   }
 *
 *   catch (Exception e)
 *   {
 *     // We use this to catch anything which we haven't catered
 *     // for.  Use the LogManager to output details, along with
 *     // a full stack trace, as an error.
 *
 *     hadError = true;
 *   }
 *
 *   finally
 *   {
 *     hadError = hadError || IncidentHandler.instance().getWorstAction() != ConverterException.Action.None;
 *     if (hadError) try { delete outputs etc } catch (Exception e) {}
 *   }
 * }
 * </code>
 * <p>
 *
 * In <i>doInitialisation</i>, if anything under
 * <i>doTheInitialisation</i> raises an exception indicating
 * abandon-run-now or abandon-file-now, the present class will
 * immediately raise either ConverterExceptionAbandonRun or
 * ConverterExceptionAbandonFile.  Both of these inherit from
 * ConverterExceptionAbandonSomething, and so will be trapped
 * immediately in the catch clause.  You can do what you like
 * in the catch clause.  In the example below, I have merely
 * opted to rethrow the exception, thus passing responsibility
 * to the main program.  (I could, of course, have achieved the
 * same effect simply by omitting the catch clause altogether,
 * but its presence makes it clearer that we are consciously
 * dealing with run-level errors here.)
 * <p>
 *
 * If anything raises an exception indicating abandon-run-whenever or
 * abandon-file-whenever, the present class merely records the fact.
 * The call to <i>handleAnyDeferredAction</i> then arranges for
 * ConverterExceptionAbandonRun or ConverterExceptionAbandonFile to
 * be raised, which again is picked up by the catch clause.
 * <p>
 *
 * <code>
 * doInitialisation ()
 * {
 *   try
 *   {
 *     doTheInitialisation();
 *     IncidentHandler.instance().handleAnyDeferredAction();
 *   }
 *   catch (ConverterExceptionAbandonSomething e)
 *   {
 *     throw e; // Main program will know there's been an error.
 *   }
 * }
 * </code>
 * <p>
 * 
 * <i>processFiles</i> handles any run-level exceptions.  As before,
 * you can do what you like in the catch clause, but I've merely
 * opted to rethrow the exception.
 * <p>
 *
 * <i>ProcessFiles1</i> runs over each file.  It starts by intialising
 * the present class, so that it thinks there are no file-level
 * actions outstanding (strictly not necessary because of the other
 * processing here, but better safe than sorry).  It then processes
 * the file.  If that processing causes the <i>file</i> to be
 * abandoned immediately, the catch clause will trap the situation.
 * If the processing causes the <i>run</i> to be abandoned
 * immediately, the catch clause in <i>processFiles</i> will pick it
 * up.  If the processing causes the <i>file</i> to be abandoned
 * "whenever", the call to <i>handleAnyDeferredAction</i> will cause a
 * ConverterExceptionAbandonFile to be raised, which again will be
 * picked up in the local catch clause.  And if it causes the
 * <i>run</i> to be abanoned whenever, <i>handleAnyDeferredAction</i>
 * will cause a ConverterExceptionAbandonRun to be raised, which will
 * be handled in the catch clause in <i>processFiles</i>.
 * <p>
 * 
 * <code>
 * processFiles ()
 * {
 *   try
 *   {
 *     processFiles1();
 *   }
 *   catch (ConverterExceptionAbandonRun e)
 *   {
 *     throw e;
 *   }
 * }
 *
 *
 *
 * processFiles1 ()
 * {
 *   for each file
 *   try
 *   {
 *     IncidentHandler.instance().resetForNewFile();
 *     processFile();
 *     IncidentHandler.instance().handleAnyDeferredAction();
 *   }
 *   catch (ConverterExceptionAbandonFile e)
 *   {
 *     // Tidy up this file.
 *   }
 * }
 * </code>
 * <p>
 * 
 * Finally, note that you don't <i>have</i> to raise exceptions in
 * order to invoke the processing here.  You can interact with the
 * LogManager directly to handle logging, and you can call
 * <i>setAction</i> here to record a deferred action, or to arrange
 * for an action to be carried out immediately.
 * 
 * @author ARA "Jamie" Jamieson
 */

public class IncidentHandler
{
  /****************************************************************************/
  /****************************************************************************/
  /**                                                                        **/
  /**                                Public                                  **/
  /**                                                                        **/
  /****************************************************************************/
  /****************************************************************************/

  /****************************************************************************/
  /**
      Singleton.

      @return Singleton instance.
   */

  public static IncidentHandler instance ()
  {
    if (null == m_Instance) m_Instance = new IncidentHandler();
    return m_Instance;
  }


  /****************************************************************************/
  /**
   * Accessor.
   * 
   * @return Action.
   */
  
  public ConverterException.Action getAction ()
  {
    return m_Action;
  }
  
  
  /****************************************************************************/
  /**
   * Accessor.
   * 
   * @return Worst action.
   */
  
  public ConverterException.Action getWorstAction ()
  {
    return m_WorstAction;
  }
  
  
  /****************************************************************************/
  /**
   * Carry out any deferred actions.
   */
  
  public void handleAnyDeferredAction ()
  {
    switch (m_Action)
    {
      // No need to worry about Abandon*Now, because they're never deferred.
      case AbandonRunWhenever:  m_Action = ConverterException.Action.None; throw new ConverterExceptionAbandonRun();
      case AbandonFileWhenever: m_Action = ConverterException.Action.None; throw new ConverterExceptionAbandonFile();
    }
  }
  
  
  /****************************************************************************/
  /**
     Process the details from an exception.

     @param e ConverterException.
  */

  public void handleException (ConverterException e) throws IOException
  {
    switch (e.getLog())
    {
      case Error:       LogManager.instance().error(e);       break;
      case Warning:     LogManager.instance().warning(e);     break;
      case Information: LogManager.instance().information(e); break;
    }

    setAction(e.getAction());
  }
  
  
  /****************************************************************************/
  /**
   * Reset for a new file.
   */
  
  public void resetForNewFile ()
  {
    if (m_Action.getOrdinal() <= ConverterException.Action.AbandonFileNow.getOrdinal())
      m_Action = ConverterException.Action.None;
    LogManager.instance().resetCountsForFile();
  }
  
  
  /****************************************************************************/
  /**
   * Reset for a new run.  (I doubt there will be any reason to call this.)
   */
  
  public void resetForNewRun ()
  {
    m_Action = ConverterException.Action.None;
    m_WorstAction = ConverterException.Action.None;
  }
  
  
  /****************************************************************************/
  /**
   * Sets the action to be the worse of what it already is and the new action,
   * and processes immediately where appropriate.
   */
  
  public void setAction (ConverterException.Action action)
  {
    if (action.getOrdinal() > m_Action.getOrdinal()) m_Action = action;
    if (action.getOrdinal() > m_WorstAction.getOrdinal()) m_WorstAction = action;

    switch (m_Action)
    {
      case AbandonRunNow:  m_Action = ConverterException.Action.None; throw new ConverterExceptionAbandonRun();
      case AbandonFileNow: m_Action = ConverterException.Action.None; throw new ConverterExceptionAbandonFile();
    }
  } 





  /****************************************************************************/
  /****************************************************************************/
  /**                                                                        **/
  /**                               Private                                  **/
  /**                                                                        **/
  /****************************************************************************/
  /****************************************************************************/

  /****************************************************************************/
  private IncidentHandler() {}


  /****************************************************************************/
  private static IncidentHandler m_Instance = null;
  private ConverterException.Action m_Action = ConverterException.Action.None;
  private ConverterException.Action m_WorstAction = ConverterException.Action.None;
}
